﻿/*
 *  This file is part of SXAPI.
 *
 *  SXAPI is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Lesser General Public License as published by
 *  the Free Software Foundation, version 3 of the License.

 *  SXAPI is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Lesser General Public License for more details.
 *  
 *  You should have received a copy of the GNU Lesser General Public License
 *  along with SXAPI.  If not, see <http://www.gnu.org/licenses/>.
 */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections.ObjectModel;

namespace StackExchangeApi
{
    /// <summary>
    /// A delay-loaded subclass of ReadOnlyCollection that supports
    /// getting an eager copy of itself.
    /// 
    /// The existance of this class may seem convoluted, but it makes implementing
    /// the Eager classes easier if there are obvious was to convert all
    /// of their exposed properties to "eager" ones.
    /// 
    /// Thus a subclass of ReadOnlyCollection that exposes AsEager()
    /// </summary>
    /// <typeparam name="T">Type held in this ReadOnlyDelayLoadedList</typeparam>
    internal sealed class ReadOnlyDelayLoadedList<T> : ReadOnlyCollection<T>
    {
        private DelayLoadedList<T> m_list;

        /// <summary>
        /// Construct a new ReadOnlyDelayLoadedList
        /// </summary>
        /// <param name="list">DelayLoadedList to wrap</param>
        internal ReadOnlyDelayLoadedList(DelayLoadedList<T> list) : base(list)
        {
            m_list = list;
        }

        /// <summary>
        /// Gets an equivalent ReadOnlyCollection that has been eagerly loaded.
        /// 
        /// Constructing such a collection implicitly loads this one.
        /// </summary>
        /// <returns></returns>
        internal ReadOnlyCollection<T> AsEager()
        {
            return new ReadOnlyCollection<T>(m_list.AsEager());
        }
    }

    /// <summary>
    /// Used to provide "chunks" of an underlying collection.
    /// 
    /// </summary>
    /// <typeparam name="T">The type contained in the underlying collection</typeparam>
    /// <param name="chunk">Which chunk to fetch</param>
    /// <param name="chunkCount">Set to the total number of chunks on return</param>
    /// <returns>All the items in the request chunk</returns>
    internal delegate IEnumerable<T> DelayedChunkProvider<T>(int chunk, out int chunkCount);

    /// <summary>
    /// Implementation of IList&lt;T&gt; which loads its contents
    /// in "chunks" as needed.
    /// </summary>
    /// <typeparam name="T">Type contained within</typeparam>
    internal sealed class DelayLoadedList<T> : IList<T>
    {
        private DelayedChunkProvider<T> m_provider;

        private Object m_load_obj = new Object();
        private volatile Dictionary<int, T> m_loaded = new Dictionary<int, T>();

        private int m_chunk_size;
        private int? m_chunk_count;

        private int? m_count;

        public T this[int index]
        {
            get
            {
                if (index >= Count)
                {
                    throw new ArgumentOutOfRangeException("index");
                }

                if (!m_loaded.ContainsKey(index))
                {
                    lock (m_load_obj)
                    {
                        if (!m_loaded.ContainsKey(index))
                        {
                            LoadToIncludeIndex(index);
                        }
                    }
                }

                return m_loaded[index];
                
            }

            set
            {
                throw new InvalidOperationException("DelayLoadedList is ReadOnly");
            }
        }

        public int Count
        {
            get
            {
                if (m_count == null)
                {
                    lock (m_load_obj)
                    {
                        if (m_count == null)
                        {
                            DetermineCount();
                        }
                    }
                }

                return (int)m_count;
            }
        }

        /// <summary>
        /// Construct a new DelayLoadedList
        /// </summary>
        /// <param name="provider">The DelayedChunkProvider used to fill in the list on demand.</param>
        /// <param name="chunkSize">The (maximum) size of the chunks provided.  All chunks provided should be of this size, except possibly the last one.</param>
        internal DelayLoadedList(DelayedChunkProvider<T> provider, int chunkSize)
        {
            m_provider = provider;
            m_chunk_size = chunkSize;
        }

        /// <summary>
        /// Create a copy of this DelayLoadedList which has been completely
        /// initialized and loaded.
        /// 
        /// Implicitly causes this list to be completely loaded as well.
        /// </summary>
        /// <returns>A copy of this DelayLoadedList, but fully initialized.</returns>
        internal IList<T> AsEager()
        {
            List<T> ret = new List<T>();

            foreach (T t in this)
                ret.Add(t);

            return this;
        }

        /// <summary>
        /// Sets m_count.
        /// 
        /// May cause up to 2 requests to m_provider.
        /// </summary>
        private void DetermineCount()
        {
            //If we haven't got the chunk count yet, go an get it.
            if (m_chunk_count == null)
                LoadToIncludeIndex(0);


            //Get the last chunk
            if (m_chunk_count != 0)
            {
                LoadToIncludeIndex((int)(m_chunk_count - 1) * m_chunk_size);
            }

            //The largest key in the last chunk (+1) is our size
            if (m_loaded.Keys.Count > 0)
            {
                m_count = m_loaded.Keys.Max() + 1;
            }
            else
            {
                m_count = 0;
            }
        }

        /// <summary>
        /// Loads the chunk necessary to find the value at the given index.
        /// 
        /// Results in a single request.
        /// </summary>
        /// <param name="index">The index in question</param>
        private void LoadToIncludeIndex(int index)
        {
            int chunk = index / m_chunk_size;

            int chunkCount;

            IEnumerable<T> bits = m_provider(chunk, out chunkCount);

            for (int i = 0; i < bits.Count(); i++)
            {
                m_loaded[chunk * m_chunk_size + i] = bits.ElementAt(i);
            }

            m_chunk_count = chunkCount;
        }

        #region IList<T> Members

        public int IndexOf(T item)
        {
            for (int i = 0; i < Count; i++)
                if (this[i].Equals(item))
                    return i;

            return -1;
        }

        public void Insert(int index, T item)
        {
            throw new InvalidOperationException("DelayLoadedList is ReadOnly");
        }

        public void RemoveAt(int index)
        {
            throw new InvalidOperationException("DelayLoadedList is ReadOnly");
        }

        #endregion

        #region ICollection<T> Members

        public void Add(T item)
        {
            throw new InvalidOperationException("DelayLoadedList is ReadOnly");
        }

        public void Clear()
        {
            throw new InvalidOperationException("DelayLoadedList is ReadOnly");
        }

        public bool Contains(T item)
        {
            foreach (T t in this)
                if (item.Equals(t))
                    return true;

            return false;
        }

        public void CopyTo(T[] array, int arrayIndex)
        {
            for (int i = 0; i < Count; i++)
            {
                array[arrayIndex + i] = this[i];
            }
        }

        public bool IsReadOnly
        {
            get { return true; }
        }

        public bool Remove(T item)
        {
            throw new InvalidOperationException("DelayLoadedList is ReadOnly");
        }

        #endregion

        #region IEnumerable<T> Members

        public IEnumerator<T> GetEnumerator()
        {
            return new DelayedEnumerator<T>(this);
        }

        #endregion

        #region IEnumerable Members

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        #endregion
    }

    /// <summary>
    /// IEnumerator&lt;T&gt; for DelayLoadedList
    /// </summary>
    /// <typeparam name="T"></typeparam>
    internal sealed class DelayedEnumerator<T> : IEnumerator<T>
    {
        private DelayLoadedList<T> m_list;
        private int m_index;

        object System.Collections.IEnumerator.Current
        {
            get { return Current; }
        }

        public T Current
        {
            get;
            internal set;
        }

        /// <summary>
        /// Construct a new DelayedEnumerator
        /// </summary>
        /// <param name="list">The list to enumerate</param>
        internal DelayedEnumerator(DelayLoadedList<T> list)
        {
            m_list = list;
            m_index = -1;
        }

        public bool MoveNext()
        {
            m_index++;

            if (m_index >= m_list.Count)
                return false;

            Current = m_list[m_index];

            return true;
        }

        public void Reset()
        {
            m_index = -1;
        }


        public void Dispose()
        {
            //Do Nothing
        }
    }
}
